#include "iniparser.h"
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/termios.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <pthread.h>

typedef struct fwd_param
{
    char    src_addr[16];
    int32_t src_port;
    char    src_nicx[32];
    int32_t tmo_recv;
    char    dst_addr[16];
    int32_t dst_port;
    char    dst_nicx[32];
    char    cut_exec[256];
    char    rev_exec[256];
}FWD_PARAM, *PFWD_PARAM;

void * rev_fwd(void *param)
{
    struct fwd_param *fwd = (struct fwd_param*)param;
    int fd_r = -1;
    if((fd_r = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
        printf("create socket failed\n");
        goto fdfail;
    }

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htonl(fwd->src_port);
    if (bind(fd_r, (struct sockaddr *)&addr , sizeof(addr) ) == -1 )
	{
		printf ( "can't bind multicast port!\n" ) ;
		goto fdfail;
	}

    struct ip_mreq mreq;
    memset ( &mreq , 0 , sizeof (struct ip_mreq ) ) ;
	mreq.imr_multiaddr.s_addr = inet_addr(fwd->src_addr);
    if(fwd->src_nicx)
    {
        mreq.imr_interface.s_addr = inet_addr(fwd->src_nicx);
    }
    else
    {
        mreq.imr_interface.s_addr = INADDR_ANY;
    }	
	if(setsockopt(fd_r, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1 )
	{
		printf("can't join the multicast group!\n" ) ;
		goto fdfail;
	}

    int fd_w = -1;
    if((fd_w = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
        printf("create send data socket failed\n");
        goto fdfail;
    }

    if(fwd->dst_nicx)
    {
        struct sockaddr_in addr = {0};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(fwd->dst_nicx);
        addr.sin_port = 0;
        if (bind(fd_w, (struct sockaddr *)&addr , sizeof(addr) ) == -1 )
        {
            printf ( "can't bind dst nic!\n" ) ;
            goto fdfail ;
        }
    }

    struct sockaddr_in dst_addr;
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_addr.s_addr = inet_addr(fwd->dst_addr);
    dst_addr.sin_port = htons(fwd->dst_port);

    fd_set rset;
    struct timeval rec_tmo ;
    int tmo_count = 0;
    char data[1500];
    int  recv_state = 0;
    while(1)
    {
        FD_ZERO ( &rset ) ;
		FD_SET ( fd_r , &rset) ;
		rec_tmo.tv_sec = fwd->tmo_recv / 1000 ;
		rec_tmo.tv_usec = fwd->tmo_recv % 1000 * 1000;
		int ret = select ( fd_r + 1 , &rset , NULL , NULL , &rec_tmo ) ;
		if ( ret == -1 ) 
		{
			continue;
		}
		else if (ret == 0 )
		{
            if(tmo_count % 5 == 0 )
            {
                //离开组
                setsockopt (fd_r, IPPROTO_IP, IP_DROP_MEMBERSHIP, 
                &mreq , sizeof(mreq)) ;
            }
            if(tmo_count % 6 == 0 )
            {
                //加入组
                setsockopt (fd_r, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                &mreq ,sizeof(mreq)) ;
            }
            tmo_count++;
            if(recv_state == 1)
            {
                system(fwd->cut_exec);
                recv_state = 0;
            }
			continue;
		}
		else	//处理接收数据
		{
            tmo_count = 0;
            int res = recv(fd_r, data, sizeof data, 0);
            if(res > 0)
            {
                sendto(fd_w, data, res, 0, (struct sockaddr*)&dst_addr, sizeof dst_addr);
                if(recv_state == 0)
                {
                    recv_state = 1;
                    system(fwd->rev_exec);
                }
            }
        }
    }

fdfail:
    if(fd_r > 0)
    {
        close(fd_r);
    }

    if(fd_w > 0)
    {
        close(fd_w);
    }

    return NULL;
}

int main(int argc, char ** argv)
{
    if(argc < 2)
    {
        printf("usage: tsfwd <config_file>");
        return 0;
    }

    dictionary * ini = iniparser_load(argv[1]);
    if(ini == NULL)
    {
        printf("cannot parse %s\n", argv[1]);
        return 0;
    }

    int cnt = iniparser_getint(ini, "GLOBAL:STREAM_COUNT", -1);
    char dict[128] = {0};
    int idx;
    for(idx = 0; idx < cnt; idx++)
    {
        sprintf(dict, "STREAM%02d:SRC_ADDR", idx + 1);
        const char * src_addr = iniparser_getstring(ini, dict, NULL);
        if(inet_addr(src_addr) == INADDR_NONE)
        {
            printf("invalid src addr in %s\n", dict);
            continue;
        }

        sprintf(dict, "STREAM%02d:SRC_PORT", idx + 1);
        int src_port = iniparser_getint(ini, dict, 0);
        if(src_port == 0 || src_port >= 65535)
        {
            printf("invalid src port in %s\n", dict);
            continue;
        }

        sprintf(dict, "STREAM%02d:SRC_NICX", idx + 1);
        const char *src_nicx  =  iniparser_getstring(ini, dict, NULL);

        sprintf(dict, "STREAM%02d:TMO_RECV", idx + 1);
        int tmo_recv = iniparser_getint(ini, dict, 5000);
        if(tmo_recv <= 1000)
        {
            tmo_recv = 1000;
            printf("use default tmo_recv value!\n");
        }

        sprintf(dict, "STREAM%02d:DST_ADDR", idx + 1);
        const char *dst_addr = iniparser_getstring(ini, dict, NULL);
        if(inet_addr(dst_addr) == INADDR_NONE)
        {
            printf("invalid dst_addr in %s\n", dict);
            continue;
        }

        sprintf(dict, "STREAM%02d:DST_PORT", idx + 1);
        int dst_port = iniparser_getint(ini, dict, 0);
        if(dst_port == 0 || dst_port >= 65535)
        {
            printf("invalid dst_port in %s\n", dict);
            continue;
        }

        sprintf(dict, "STREAM%02d:DST_NICX", idx + 1);
        const char *dst_nicx = iniparser_getstring(ini, dict, NULL);

        sprintf(dict, "STREAM%02d:CUT_EXEC", idx + 1);
        const char * cut_exec = iniparser_getstring(ini, dict, NULL);

        sprintf(dict, "STREAM%02d:REV_EXEC", idx + 1);
        const char * rev_exec = iniparser_getstring(ini, dict, NULL);

        struct fwd_param * fwd = (struct fwd_param*)malloc(sizeof (struct fwd_param));
        if(!fwd)
        {
            printf("cannot alloc memory\n");
            continue;
        }

        strcpy(fwd->src_addr, src_addr);
        fwd->src_port = src_port;
        if(src_nicx)
        {
            strcpy(fwd->src_nicx, src_nicx);
        }
        fwd->tmo_recv = tmo_recv;
        strcpy(fwd->dst_addr, dst_addr);
        fwd->dst_port = dst_port;
        if(dst_nicx)
        {
            strcpy(fwd->dst_nicx, dst_nicx);
        }
        if(cut_exec)
        {
            strcpy(fwd->cut_exec, cut_exec);
        }

        if(rev_exec)
        {
            strcpy(fwd->rev_exec, rev_exec);
        }

        pthread_t tid;
        pthread_create(&tid, NULL, rev_fwd, fwd);
    }

    for(;;)
    {
        sleep(1000);
    }
}
